package com.ease.framework {

   import com.ease.util.Diagnostics;
   import com.ease.util.ReflectionUtil;
   import com.ease.util.events.MethodReturnEvent;
   import com.ease.framework.priv.ResponseInfo;

   import flash.events.Event;
   import flash.utils.Dictionary;

   public class CallbackUtil {
      public static function
         dispatchResponseUsingRequestContext(ev:MethodReturnEvent):void {
         var request:Object = ev.callContext;
         dispatchResponse( request, ev );
      }

      public static function createResponseFromRequest( request:Object,
                                                        result:Object,
                                                        error:Error ):Event {
         var outEvent:Event;
         var outEventId:String;
         // see if there's a reponse type specified on the request itself
         if (request is Event) {
            var requestEv:Event = Event( request );
            // get the response type from the original request
            var responseTagArgs:Dictionary =
               ReflectionUtil.getClassTagData( requestEv, "Response", "request",
                  requestEv.type );
            outEventId = responseTagArgs["name"];
            var outEventType:String = responseTagArgs["type"];
            var constructorArg:* = outEventId;
            if (outEventType == null) {
               // This is the case where the response event class is supposed to be the same
               // as the request event class. Hence we create the response event by cloning
               // the request event.
               outEvent = ReflectionUtil.createShallowClone( requestEv,
                  constructorArg ) as Event;
            } else {
               // The case where the response event type has been explicitly specified.
               var moduleReference:* = request;
               outEvent = ReflectionUtil.newInstance( outEventType, moduleReference,
                  constructorArg ) as Event;
               if (outEventType && !outEvent) {
                  Diagnostics.assert( false, "Failed to instantiate: " + outEventType );
                  // we recover from the error below by creating a default response
               }
            }
         }
         if (! outEvent) {
            outEvent = newDefaultResponse( outEventId, result, error, request as Event );
         }
         var eventBus:EventBus = getEventBusFrom( request );
         var originalRequest:Event =
            eventBus.originalFromRedispatchedEvent( request as Event );
         var injectableRequest:Object = (originalRequest == null)? request : originalRequest;
         ResponseInfo.setTaggedPropertiesOnResponse(
            outEvent, injectableRequest, result, error );
         return outEvent;
      }

      /**
       * @private
       *
       * Dispatches a response event for the given request and the given result of a method
       * invocation (which is supposed to have resulted from the request).
       */
      internal static function dispatchResponse( request:Object,
                                                 ev:MethodReturnEvent ):void {
         var outEvent:Event = createResponseFromRequest( request, ev.result, ev.error );
         var eventBus:EventBus = getEventBusFrom( request );
         eventBus.dispatchEvent( outEvent );
      }

      private static function getEventBusFrom( request:Object ):EventBus {
         // Get the event bus off of the request. The global default bus should only be
         // used if we can't find a bus with reference to the original event.
         var eventBus:EventBus = FrinjeUtil.getAssociatedEventBus( request );
         return (eventBus == null)? EventBus.instance : eventBus;
      }

      /**
       * The fallback case; when there's no [Response] event type specified.
       */
      private static function
         newDefaultResponse( outEventId:String,
                             result:Object,
                             error:Error,
                             originalRequestEv:Event ):CommandCompletionEvent {
         if (! outEventId) {
            if (originalRequestEv) {
               outEventId = originalRequestEv.type + "Response";
            } else {
               outEventId = "commandCompletion";
            }
         }
         var outEvent:CommandCompletionEvent = new CommandCompletionEvent( outEventId );
         outEvent.error = error;
         outEvent.result = result;
         outEvent.request = originalRequestEv;
         return outEvent;
      }
   }

}